package org.example.service.impl;

import org.example.entity.AccessStatistics;
import org.example.mapper.AccessStatisticsMapper;
import org.example.service.AccessStatisticsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 访问统计服务实现类
 * 
 * @author Your Name
 * @since 2024-07-01
 */
@Service
public class AccessStatisticsServiceImpl implements AccessStatisticsService {
    
    @Autowired
    private AccessStatisticsMapper accessStatisticsMapper;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    // Redis key前缀 - 详细注释说明每个前缀的用途
    private static final String REDIS_ACCESS_COUNT_PREFIX = "access_count:";        // 总访问计数
    private static final String REDIS_ACCESS_RECORD_PREFIX = "access_record:";      // 访问记录缓存
    private static final String REDIS_DAILY_COUNT_PREFIX = "daily_count:";          // 日访问计数
    private static final String REDIS_HOURLY_COUNT_PREFIX = "hourly_count:";        // 小时访问计数
    private static final String REDIS_UNIQUE_IP_PREFIX = "unique_ip:";             // 独立IP计数
    private static final String REDIS_ACCESS_QUEUE_PREFIX = "access_queue:";       // 访问记录队列
    
    // Redis过期时间配置
    private static final int REDIS_EXPIRE_HOURS = 24;      // 访问记录缓存24小时
    private static final int REDIS_DAILY_EXPIRE_DAYS = 7;  // 日统计缓存7天
    private static final int REDIS_HOURLY_EXPIRE_HOURS = 48; // 小时统计缓存48小时
    
    // 内存缓存，用于临时存储高频访问数据
    private static final ConcurrentHashMap<String, AtomicLong> MEMORY_CACHE = new ConcurrentHashMap<>();
    private static final int MEMORY_CACHE_SYNC_THRESHOLD = 100; // 内存缓存同步阈值
    
    // 日期时间格式化器
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter HOUR_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH");
    
    @Override  // 实现AccessStatisticsService接口中的recordAccess方法
    @Async     // 异步执行注解，让这个方法在后台线程中执行，不阻塞主请求线程
    public void recordAccess(String shortCode, String ipAddress, String userAgent, String referer) {
        try {  // 使用try-catch包装整个方法，确保异常不会影响主业务流程
            // ==================== 第一步：内存缓存计数（最高性能） ====================
            // 使用内存缓存进行即时计数，避免频繁的Redis网络调用
            String memoryKey = shortCode;  // 使用短链接编码作为内存缓存的key
            // 从内存缓存中获取计数器，如果不存在则创建一个新的AtomicLong(0)
            // computeIfAbsent确保线程安全，避免并发创建多个计数器
            AtomicLong memoryCounter = MEMORY_CACHE.computeIfAbsent(memoryKey, k -> new AtomicLong(0));
            // 原子性地将计数器加1并返回新值，这是线程安全的操作
            long currentCount = memoryCounter.incrementAndGet();
            
            // ==================== 第二步：Redis多维度统计 ====================
            // 1. 总访问计数 - 使用Redis INCR原子操作
            String totalCountKey = REDIS_ACCESS_COUNT_PREFIX + shortCode;  // 构建Redis key，格式：access_count:abc123
            redisTemplate.opsForValue().increment(totalCountKey);  // Redis INCR操作，原子性地将值加1
            redisTemplate.expire(totalCountKey, REDIS_EXPIRE_HOURS, TimeUnit.HOURS);  // 设置24小时过期时间
            
            // 2. 日访问计数 - 按日期统计
            String today = LocalDateTime.now().format(DATE_FORMATTER);  // 获取当前日期，格式：2024-01-15
            String dailyCountKey = REDIS_DAILY_COUNT_PREFIX + shortCode + ":" + today;  // 构建日统计key，格式：daily_count:abc123:2024-01-15
            redisTemplate.opsForValue().increment(dailyCountKey);  // 对今日访问量进行INCR操作
            redisTemplate.expire(dailyCountKey, REDIS_DAILY_EXPIRE_DAYS, TimeUnit.DAYS);  // 设置7天过期时间
            
            // 3. 小时访问计数 - 按小时统计
            String currentHour = LocalDateTime.now().format(HOUR_FORMATTER);  // 获取当前小时，格式：2024-01-15-14
            String hourlyCountKey = REDIS_HOURLY_COUNT_PREFIX + shortCode + ":" + currentHour;  // 构建小时统计key，格式：hourly_count:abc123:2024-01-15-14
            redisTemplate.opsForValue().increment(hourlyCountKey);  // 对当前小时访问量进行INCR操作
            redisTemplate.expire(hourlyCountKey, REDIS_HOURLY_EXPIRE_HOURS, TimeUnit.HOURS);  // 设置48小时过期时间
            
            // 4. 独立IP计数 - 使用Redis Set存储唯一IP
            String uniqueIpKey = REDIS_UNIQUE_IP_PREFIX + shortCode + ":" + today;  // 构建独立IP统计key，格式：unique_ip:abc123:2024-01-15
            redisTemplate.opsForSet().add(uniqueIpKey, ipAddress);  // 将IP地址添加到Set中，自动去重
            redisTemplate.expire(uniqueIpKey, REDIS_DAILY_EXPIRE_DAYS, TimeUnit.DAYS);  // 设置7天过期时间
            
            // ==================== 第三步：访问记录缓存 ====================
            // 缓存访问记录，用于后续批量同步到数据库
            String recordKey = REDIS_ACCESS_RECORD_PREFIX + shortCode + ":" + System.currentTimeMillis();  // 构建记录key，使用时间戳确保唯一性
            // 将访问信息格式化为字符串，用|分隔：IP|UserAgent|Referer|访问时间
            String recordValue = String.format("%s|%s|%s|%s", ipAddress, userAgent, referer, LocalDateTime.now());
            redisTemplate.opsForValue().set(recordKey, recordValue, REDIS_EXPIRE_HOURS, TimeUnit.HOURS);  // 存储访问记录，24小时过期
            
            // ==================== 第四步：内存缓存同步检查 ====================
            // 当内存缓存达到阈值时，同步到Redis
            if (currentCount % MEMORY_CACHE_SYNC_THRESHOLD == 0) {  // 每100次访问同步一次内存缓存到Redis
                syncMemoryCacheToRedis(shortCode, currentCount);  // 调用同步方法，确保数据一致性
            }
            
            // ==================== 第五步：异步数据库落库 ====================
            // 创建访问统计记录并异步保存到数据库
            AccessStatistics accessStatistics = new AccessStatistics(shortCode, ipAddress, userAgent, referer);  // 创建访问统计实体对象
            accessStatisticsMapper.insert(accessStatistics);  // 插入数据库，由于方法有@Async注解，这里在后台线程执行，不阻塞主流程
            
        } catch (Exception e) {  // 捕获所有异常，确保不会影响主业务流程
            // 记录错误日志，但不影响主流程
            System.err.println("记录访问统计失败 - shortCode: " + shortCode + ", error: " + e.getMessage());  // 打印错误信息到控制台
            e.printStackTrace();  // 打印完整的异常堆栈信息，便于调试
        }
    }
    
    @Override
    public List<AccessStatistics> getAccessStatistics(String shortCode) {
        return accessStatisticsMapper.selectByShortCode(shortCode);
    }
    
    @Override
    public List<AccessStatistics> getAccessStatisticsByTimeRange(String shortCode, LocalDateTime startTime, LocalDateTime endTime) {
        return accessStatisticsMapper.selectByTimeRange(shortCode, startTime, endTime);
    }
    
    @Override
    public int getTotalAccessCount(String shortCode) {
        // ==================== 第一步：从内存缓存获取（最快） ====================
        AtomicLong memoryCounter = MEMORY_CACHE.get(shortCode);
        if (memoryCounter != null) {
            long memoryCount = memoryCounter.get();
            if (memoryCount > 0) {
                return (int) memoryCount;
            }
        }
        
        // ==================== 第二步：从Redis获取 ====================
        String countKey = REDIS_ACCESS_COUNT_PREFIX + shortCode;
        String countStr = redisTemplate.opsForValue().get(countKey);
        
        if (countStr != null) {
            try {
                return Integer.parseInt(countStr);
            } catch (NumberFormatException e) {
                // 如果Redis中的数据无效，从数据库获取
                System.err.println("Redis计数数据格式错误: " + countStr);
            }
        }
        
        // ==================== 第三步：从数据库获取并更新缓存 ====================
        int count = accessStatisticsMapper.countByShortCode(shortCode);
        
        // 更新Redis缓存
        redisTemplate.opsForValue().set(countKey, String.valueOf(count), REDIS_EXPIRE_HOURS, TimeUnit.HOURS);
        
        // 更新内存缓存
        MEMORY_CACHE.put(shortCode, new AtomicLong(count));
        
        return count;
    }
    
    @Override
    public int getAccessCountByTimeRange(String shortCode, LocalDateTime startTime, LocalDateTime endTime) {
        return accessStatisticsMapper.countByTimeRange(shortCode, startTime, endTime);
    }
    
    @Override
    public void cleanExpiredData(int expireDays) {
        try {
            LocalDateTime expireTime = LocalDateTime.now().minusDays(expireDays);
            accessStatisticsMapper.deleteExpiredData(expireTime);
        } catch (Exception e) {
            System.err.println("清理过期数据失败: " + e.getMessage());
        }
    }
    
    /**
     * 批量保存访问统计记录到数据库
     * 这个方法可以在定时任务中调用，将Redis中的统计数据批量同步到数据库
     */
    public void batchSaveToDatabase() {
        try {
            // 获取所有访问计数的key
            Set<String> keys = redisTemplate.keys(REDIS_ACCESS_COUNT_PREFIX + "*");
            
            if (keys != null && !keys.isEmpty()) {
                List<AccessStatistics> batchList = new ArrayList<>();
                
                for (String key : keys) {
                    String shortCode = key.substring(REDIS_ACCESS_COUNT_PREFIX.length());
                    String countStr = redisTemplate.opsForValue().get(key);
                    
                    if (countStr != null) {
                        try {
                            int count = Integer.parseInt(countStr);
                            
                            // 创建访问统计记录
                            AccessStatistics accessStatistics = new AccessStatistics();
                            accessStatistics.setShortCode(shortCode);
                            accessStatistics.setAccessTime(LocalDateTime.now());
                            accessStatistics.setIpAddress("batch_sync");
                            
                            batchList.add(accessStatistics);
                            
                            // 如果批量列表达到一定大小，执行批量插入
                            if (batchList.size() >= 100) {
                                accessStatisticsMapper.batchInsert(batchList);
                                batchList.clear();
                            }
                        } catch (NumberFormatException e) {
                            // 忽略无效数据
                        }
                    }
                }
                
                // 插入剩余的记录
                if (!batchList.isEmpty()) {
                    accessStatisticsMapper.batchInsert(batchList);
                }
            }
        } catch (Exception e) {
            System.err.println("批量保存访问统计失败: " + e.getMessage());
        }
    }
    
    // ==================== 新增的点击量统计方法 ====================
    
    /**
     * 获取日访问量统计
     * 
     * @param shortCode 短链接编码
     * @param date 日期，格式：yyyy-MM-dd
     * @return 日访问量
     */
    public int getDailyAccessCount(String shortCode, String date) {
        String dailyCountKey = REDIS_DAILY_COUNT_PREFIX + shortCode + ":" + date;
        String countStr = redisTemplate.opsForValue().get(dailyCountKey);
        
        if (countStr != null) {
            try {
                return Integer.parseInt(countStr);
            } catch (NumberFormatException e) {
                System.err.println("日访问量数据格式错误: " + countStr);
            }
        }
        
        // 如果Redis中没有数据，从数据库统计
        LocalDateTime startTime = LocalDateTime.parse(date + "T00:00:00");
        LocalDateTime endTime = LocalDateTime.parse(date + "T23:59:59");
        return accessStatisticsMapper.countByTimeRange(shortCode, startTime, endTime);
    }
    
    /**
     * 获取小时访问量统计
     * 
     * @param shortCode 短链接编码
     * @param hour 小时，格式：yyyy-MM-dd-HH
     * @return 小时访问量
     */
    public int getHourlyAccessCount(String shortCode, String hour) {
        String hourlyCountKey = REDIS_HOURLY_COUNT_PREFIX + shortCode + ":" + hour;
        String countStr = redisTemplate.opsForValue().get(hourlyCountKey);
        
        if (countStr != null) {
            try {
                return Integer.parseInt(countStr);
            } catch (NumberFormatException e) {
                System.err.println("小时访问量数据格式错误: " + countStr);
            }
        }
        
        // 如果Redis中没有数据，从数据库统计
        LocalDateTime startTime = LocalDateTime.parse(hour.replace("-", "T") + ":00:00");
        LocalDateTime endTime = LocalDateTime.parse(hour.replace("-", "T") + ":59:59");
        return accessStatisticsMapper.countByTimeRange(shortCode, startTime, endTime);
    }
    
    /**
     * 获取独立IP访问量统计
     * 
     * @param shortCode 短链接编码
     * @param date 日期，格式：yyyy-MM-dd
     * @return 独立IP数量
     */
    public long getUniqueIpCount(String shortCode, String date) {
        String uniqueIpKey = REDIS_UNIQUE_IP_PREFIX + shortCode + ":" + date;
        Long count = redisTemplate.opsForSet().size(uniqueIpKey);
        return count != null ? count : 0;
    }
    
    /**
     * 同步内存缓存到Redis
     * 当内存缓存达到阈值时调用，确保数据一致性
     * 
     * @param shortCode 短链接编码
     * @param currentCount 当前计数
     */
    private void syncMemoryCacheToRedis(String shortCode, long currentCount) {
        try {
            String totalCountKey = REDIS_ACCESS_COUNT_PREFIX + shortCode;
            redisTemplate.opsForValue().set(totalCountKey, String.valueOf(currentCount), REDIS_EXPIRE_HOURS, TimeUnit.HOURS);
            System.out.println("内存缓存同步到Redis - shortCode: " + shortCode + ", count: " + currentCount);
        } catch (Exception e) {
            System.err.println("内存缓存同步到Redis失败: " + e.getMessage());
        }
    }
    
    /**
     * 获取实时访问统计概览
     * 包含总访问量、今日访问量、独立IP数等
     * 
     * @param shortCode 短链接编码
     * @return 统计概览信息
     */
    public String getAccessStatisticsOverview(String shortCode) {
        try {
            String today = LocalDateTime.now().format(DATE_FORMATTER);
            
            int totalCount = getTotalAccessCount(shortCode);
            int dailyCount = getDailyAccessCount(shortCode, today);
            long uniqueIpCount = getUniqueIpCount(shortCode, today);
            
            return String.format(
                "短链接 %s 访问统计:\n" +
                "- 总访问量: %d\n" +
                "- 今日访问量: %d\n" +
                "- 今日独立IP数: %d",
                shortCode, totalCount, dailyCount, uniqueIpCount
            );
        } catch (Exception e) {
            return "获取访问统计失败: " + e.getMessage();
        }
    }
}
